//
// Created by cube on 2020/8/25.
//

#ifndef COMPILERDOMEA1_SIGNALEVENTDRIVERS_H
#define COMPILERDOMEA1_SIGNALEVENTDRIVERS_H

#include <functional>
#include <mutex>
#include <vector>

#include "EventSystem.h"

#define LIB_SIGNAL(T,...) Signal<__VA_ARGS__> T;

namespace Lib{

    class EventDriver;

    struct SignalBase {
        enum Option {
            Local,
            Threads
        };
    };

    template <typename ...Args>
    struct Signal : public SignalBase{

        struct SlotType : public std::function<void(Args...)>{
            using std::function<void(Args...)>::function;
            friend bool operator <(const SlotType& slot_a,const SlotType& slot_b){
                return slot_a.target_type().hash_code() < slot_b.target_type().hash_code();
            }
            friend bool operator >(const SlotType& slot_a,const SlotType& slot_b){
                return slot_a.target_type().hash_code() > slot_b.target_type().hash_code();
            }
        };

    public:
        Signal():__loop(loop()){}
        Signal(EventLoop*& _loop):__loop(_loop){}

        void connect(SlotType slot,Option opt = Threads){
            if(opt == Local)__slots_local.lock()->insert(slot);
            else if(opt == Threads)__slots_threads.lock()->insert(slot);
        }

        void connect(Signal& signal){__signals.lock()->insert(&signal);}

        void disConnect(SlotType slot,Option opt = Threads){
            if(opt == Local)__slots_local.lock()->erase(slot);
            else if(opt == Threads)__slots_threads.lock()->erase(slot);
        }

        void disConnect(Signal& signal){__signals.lock()->erase(&signal);}

        friend bool operator <(const Signal& signal_a,const Signal& signal_b) {
            if(true){
                std::lock(signal_a.__slots_local.mutex(),signal_b.__slots_local.mutex());
                if(signal_a.__slots_local < signal_b.__slots_local)return true;
            }
            if(true){
                std::lock(signal_a.__slots_threads.mutex(),signal_b.__slots_threads.mutex());
                if(signal_a.__slots_threads < signal_b.__slots_threads)return true;
            }
            if(true){
                std::lock(signal_a.__signals.mutex(),signal_b.__signals.mutex());
                if(signal_a.__signals < signal_b.__signals)return true;
            }
            return false;
        }

    protected:
        void emit(Args ...args) {
            long index = 0;
            Signal* signal = nullptr;
            SlotType fun;
            while (__slots_threads.ergodic_at(index++,fun))
                __loop->sendEvent(new SignalEvent(
                        std::bind(fun,std::forward<Args>(args)...)));
            index = 0;
            while (__signals.ergodic_at(index++,signal))
                signal->emit(std::forward<Args>(args)...);
            index = 0;
            while (__slots_local.ergodic_at(index++,fun))
                fun(std::forward<Args>(args)...);
        }

        static EventLoop*& loop(){
            static EventLoop* _loop = &EventLoop::singleCase();
            return _loop;
        }

    public:
        EventLoop*&         __loop;
        TSet<SlotType> __slots_threads;
        TSet<SlotType> __slots_local;
        TSet<Signal*>  __signals;
        friend class EventDriver;
    };

    //--------------------------------------------------------------------------------------

    class EventDriver : public EventLoop::EventHandler{
    public:
        EventDriver():__loop(&EventLoop::singleCase()){}

        void setLoop(EventLoop* loop){__loop = loop;}
        EventLoop*& loop(){return __loop;}

    protected:
        void sendEvent(Event* event){loop()->sendEvent(event);}
        void sendEvent(const std::chrono::microseconds& millis,Event* event){loop()->sendEvent(millis,event);}

        void installHandler(EventLoop::EventHandler* handler){loop()->addHandler(handler);}
        void uinstallHandler(EventLoop::EventHandler* handler){loop()->eraseHandler(handler);}
        bool hasHandler(EventLoop::EventHandler* handler){return loop()->containsHandler(handler);}

        template <typename ...Args>
        void emit(Signal<Args...>& signal,Args ...args){signal.emit(std::forward<Args>(args)...);}

    private:
        EventLoop* __loop;
    };

    //----------------------------------------------------------------------------



//    template <typename SignalType = uint32_t >
//    class SignalDriver : public virtual Event_Driver{
//    public:
////        using SignalType = uint32_t;
//
//        enum class Model{
//            //Auto,
//            Local,
//            Threads
//        };
//
//    protected:
//        using Slot          = std::shared_ptr<Function_ptr>;
//        using SignalSlot    = SignalType;
//
//        struct SlotsData{
//            std::map<uint32_t ,Slot>    slots;
//            std::map<uint32_t ,Slot>    slots_local;
//            std::set<SignalSlot>        signal_slots;
//            std::shared_mutex       slots_mutex;
//            std::shared_mutex       signal_slots_mutex;
//            std::shared_mutex       slots_local_mutex;
//        };
//
//    public:
//
//        template <typename ...Args>
//        void emit(const SignalType& signal_id,Args... args){
//            std::shared_ptr<SlotsData> data = getSlotsData_read(signal_id);
//            if(data){
//                //thread
//                if(true){
//                    std::shared_lock l(data->slots_mutex);
//                    for(auto p = data->slots.cbegin();p!= data->slots.cend();p++)
//                        loop()->sendEvent(new SignalEvent(
//                                Function_ptr::make(p->second.get(),std::forward<Args>(args)...)));
//                }
//                //local
//                if(true) {
//                    std::shared_lock l(data->slots_local_mutex);
//                    for (auto p = data->slots_local.cbegin();p != data->slots_local.cend(); p++){
//                        ((Function__<Args...>*)p->second.get())->__fun(std::forward<Args>(args)...);
//                    }
//                }
//                //signal send
//                if(true) {
//                    std::shared_lock l(data->signal_slots_mutex);
//                    for (auto p = data->signal_slots.cbegin();p != data->signal_slots.cend(); p++){
//                        emit(*p,std::forward<Args>(args)...);
//                    }
//                }
//            }
//        }
//
//        template <typename ...Args>
//        void connect(const SignalType& signal_id,const std::function<void(Args...)>& slot,Model model = Model::Threads){
//            Slot slot_p = std::shared_ptr<Function_ptr>(Function_ptr::make_fun(slot));
//            auto data = getSlotsData(signal_id);
//            auto item = std::pair(slot.target_type().hash_code(),slot_p);
//            if(model == Model::Local){
//                std::unique_lock l(data->slots_local_mutex);
//                data->slots_local.insert(item);
//            }else{
//                std::unique_lock l(data->slots_mutex);
//                data->slots.insert(item);
//            }
//        }
//
//        template <typename ...Args>
//        void disConnect(const SignalType& signal_id,const std::function<void(Args...)>& slot,Model model = Model::Threads){
//            auto data = getSlotsData_read(signal_id);
//            if(data){
//                auto k = slot.target_type().hash_code();
//                if(model == Model::Local){
//                    std::unique_lock l(data->slots_local_mutex);
//                    if(data->slots_local.find(k) == data->slots_local.cend())return;
//                    data->slots_local.erase(k);
//                }else{
//                    std::unique_lock l(data->slots_mutex);
//                    if(data->slots.find(k) == data->slots.cend())return;
//                    data->slots.erase(k);
//                }
//            }
//        }
//
//        void connect(const SignalType& signal_id_a,const SignalType& signal_id_b){
//            SignalSlot slot_item = signal_id_b;
//            auto data = getSlotsData(signal_id_a);
//            std::unique_lock l(data->signal_slots_mutex);
//            data->signal_slots.insert(slot_item);
//        }
//
//        void disConnect(const SignalType& signal_id_a,const SignalType& signal_id_b){
//            auto data = getSlotsData_read(signal_id_a);
//            if(data){
//                std::unique_lock l(data->signal_slots_mutex);
//                data->signal_slots.erase(signal_id_b);
//            }
//        }
//
//    protected:
//        bool hasSlotsData(const SignalType& signal_id){
//            std::shared_lock l(__slots_mutex);
//            return __slots.find(signal_id) != __slots.cend();
//        }
//        std::shared_ptr<SlotsData>  getSlotsData(const SignalType& signal_id){
//            if(!hasSlotsData(signal_id)){
//                std::unique_lock l(__slots_mutex);
//                if(__slots.find(signal_id) == __slots.cend())
//                    __slots.insert(std::pair(signal_id,
//                            std::shared_ptr<SlotsData>(new SlotsData)));
//            }
//            std::shared_lock l(__slots_mutex);
//            return __slots.at(signal_id);
//        }
//        std::shared_ptr<SlotsData>  getSlotsData_read(const SignalType& signal_id){
//            std::shared_lock l(__slots_mutex);
//            if(__slots.find(signal_id) == __slots.cend())return nullptr;
//            return __slots.at(signal_id);
//        }
//
//    private:
//        std::map<SignalType,std::shared_ptr<SlotsData>>  __slots;
//        std::map<SignalType,std::shared_ptr<SlotsData>>  __slots_local;
//        std::shared_mutex         __slots_mutex;
//    };
}

#endif //COMPILERDOMEA1_SIGNALEVENTDRIVERS_H
